package control;

import goals.Goal;
import goals.GoalAttack;
import goals.GoalFlee;
import goals.GoalPickUp;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import utility.Vector;
import world.MainSimulator;
import world.Person;
import world.PhysicalObject;
import world.Weapon;

public class GUI extends JFrame {

	private class Lock {
		private boolean locked;

		public synchronized boolean acquire() {
			if (locked) {
				return false;
			} else {
				locked = true;
				return true;
			}
		}

		public synchronized void release() {
			locked = false;
		}
	}

	public class Output {
		private static final int MAX_LENGTH = 10000;
		private String s;

		public Output() {
			s = "";
		}

		public synchronized void push(String str) {
			s = s + str;
			if (s.length() > MAX_LENGTH) {
				s = s.substring(s.length() - 1 - MAX_LENGTH / 2, s.length() - 1);
			}
		}

		public synchronized String fetch() {
			return s;
		}
	}

	public class GameWindow extends Component {

		private static final long serialVersionUID = 7477264715941014704L;

		private MainSimulator sim;

		public GameWindow(MainSimulator sim) {
			this.sim = sim;
		}

		public void paint(Graphics g) {
			Dimension size = this.getSize();
			Vector worldSize = sim.getWorldSize();
			
			double xScale = 0.9 * size.width / worldSize.x;
			double yScale = 0.9 * size.height / worldSize.y;

			/*
			 * Scale: proportion between game screen and world. 1: Set scale to
			 * lowest of both dimensions. 2: Larger dimension gets offsets. 3:
			 * Lines drawn to indicate border of game world.
			 */
			double scale = Math.min(xScale, yScale);
			double xOffset = 0.5 * (size.width - worldSize.x * scale);
			double yOffset = 0.5 * (size.height - worldSize.y * scale);
			assert (xOffset >= 0 && yOffset >= 0);
			g.drawRect(Vector.round(xOffset), 
					Vector.round(yOffset), 
					Vector.round(worldSize.x * scale), 
					Vector.round(worldSize.y * scale));

			ArrayList<PhysicalObject> ppl = sim.getVisibleObjects();

			// width and height of drawn objects in px
			int objSize = 6;

			for (PhysicalObject o : ppl) {
				int x = Vector.round(xOffset + o.getPosition().x * scale) - objSize / 2, 
						y = Vector.round(yOffset + o.getPosition().y * scale) - objSize / 2, 
						w = objSize, 
						h = objSize;
				if (o instanceof Person) {
					Person op = (Person) o;
					if (op.getHealth() > 0) {
						g.fillRect(x, y, w, h);
						g.drawString(o.getName(), x + objSize, y + objSize);
						Goal goal = op.getActiveGoal();
						if (goal != null && goal.getTarget() != null) {
							if (goal instanceof GoalAttack) {
								g.setColor(Color.RED);
							}
							if (goal instanceof GoalFlee) {
								g.setColor(Color.GREEN);
							}
							if (goal instanceof GoalPickUp) {
								g.setColor(Color.BLUE);
							}
							g.drawLine(Vector.round(xOffset + op.getPosition().x * scale),
									Vector.round(yOffset + op.getPosition().y * scale),
									Vector.round(xOffset + goal.getTarget().getPosition().x * scale), 
									Vector.round(yOffset + goal.getTarget().getPosition().y * scale));
							g.setColor(Color.BLACK);
						}
					}
				} else {
					if (o instanceof Weapon) {
						Weapon ow = (Weapon) o;
						if (ow.getOwner() == null) {
							g.drawRect(x, y, w, h);
							g.drawString(o.getName(), x + objSize, y + objSize);
						}
					} else {
						// unknown object

						g.drawRect(x, y, w, h);
					}
				}
			}
		}
	}

	private static final long serialVersionUID = 2501807757333432066L;
	private JTextArea outputArea;
	private JTextField queryTextField;
	private JScrollPane scrollPane;
	private GameWindow gameWindow;
	private MainSimulator sim;
	private Lock inputLock;
	public Output out;

	public GUI() {
		super();

		this.setLayout(new BorderLayout());
		this.setSize(1024, 768);
		this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		this.setExtendedState(JFrame.MAXIMIZED_BOTH);
		this.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				super.windowClosing(e);
				sim.stop = true;
				sim.interrupt();
			}
		});

		Font font = new Font("Courier New", Font.PLAIN, 12);
		outputArea = new JTextArea();
		outputArea.setLineWrap(true);
		outputArea.setFont(font);
		outputArea.setEditable(false);
		outputArea.setSize(400, this.getHeight() - 100);
		scrollPane = new JScrollPane(outputArea);
		this.getContentPane().add(BorderLayout.EAST, scrollPane);

		queryTextField = new JTextField(100);
		queryTextField.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (inputLock.acquire()) {
					queryTextField.setEditable(false);
					query(queryTextField.getText());
					queryTextField.setText("");
					queryTextField.setEditable(true);
					inputLock.release();
				}
			}
		});
		this.getContentPane().add(BorderLayout.SOUTH, queryTextField);

		out = new Output();
		sim = new MainSimulator(out);

		gameWindow = new GameWindow(sim);
		gameWindow.setSize(this.getWidth() - 400, this.getHeight() - 100);
		this.getContentPane().add(BorderLayout.CENTER, gameWindow);

		inputLock = new Lock();

		sim.start();

	}

	private void query(String q) {
		sim.addQuery(q);

		out.push("-------------------------------------------------------\n");
		out.push("> " + q + "\n");

		sim.finished = false;

		while (!sim.finished) {
			try {
				Thread.sleep(100);
				outputArea.setText(out.fetch());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		outputArea.setText(out.fetch());
		this.update(getGraphics());

	}

}
